package org.brakepedal.server.service;

import lombok.extern.slf4j.Slf4j;
import org.brakepedal.server.pojo.BtInfo;
import org.brakepedal.server.support.bluetooth.BtService;
import org.brakepedal.server.support.bluetooth.BtDeviceProvider;
import org.brakepedal.server.support.exception.AlreadyPairingException;
import org.brakepedal.server.support.exception.BusinessException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.bluetooth.*;
import javax.bluetooth.UUID;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

/**
 * Date: 2024/3/31 15:57
 *
 * @author Huanyu Mark
 */
@Slf4j
@Service
public class BtPairingService {

    private final ScheduledThreadPoolExecutor scheduler;

    private final BtDeviceProvider deviceProvider;

    private final LocalDevice localDevice;

    private final DiscoveryListener listener = new StandardDiscoveryListener();

    private final Map<BtInfo, RemoteDevice> allBtInfos = new HashMap<>();

    private final AtomicBoolean scanning = new AtomicBoolean(false);

    private final DiscoveryAgent discoveryAgent;

    private UUID serviceUUID;

    private volatile boolean isPairing;

    private volatile CompletableFuture<Collection<BtService>> serviceFoundFuture;

    @Value("${bluetooth.service-uuid}")
    public void setServiceUUID(long serviceUUID) {
        this.serviceUUID = new UUID(serviceUUID);
    }

    {
        try {
            localDevice = LocalDevice.getLocalDevice();
            discoveryAgent = localDevice.getDiscoveryAgent();
        } catch (BluetoothStateException e) {
            throw new RuntimeException(e);
        }
    }

    public BtPairingService(ScheduledThreadPoolExecutor scheduler, BtDeviceProvider deviceProvider) {
        this.scheduler = scheduler;
        this.deviceProvider = deviceProvider;
    }

    public Collection<BtInfo> startScan() {
        // 启动设备发现（GIAC为一般查询访问码，表示查找所有类型的设备）
        if (!scanning.compareAndSet(false, true)) {
            return allBtInfos.keySet();
        }
        try {
            discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
        } catch (IOException e) {
            throw new BusinessException(e);
        }
        return allBtInfos.keySet();
    }

    public void stopScan(){
        if (scanning.compareAndSet(true, false)) {
            discoveryAgent.cancelInquiry(listener);
            allBtInfos.clear();
        }
    }

    public CompletableFuture<Collection<BtService>> pair(BtInfo deviceInfo) throws IOException {
        var future = new CompletableFuture<Collection<BtService>>();
        if(isPairing) {
            future.completeExceptionally(new AlreadyPairingException());
            return future;
        }
        synchronized (this){
            if(isPairing) {
                future.completeExceptionally(new AlreadyPairingException());
                return future;
            }
            isPairing = true;
        }
        var device = allBtInfos.get(deviceInfo);
        if(device == null) {
            future.completeExceptionally(new BusinessException("Device not found"));
            return future;
        }
        serviceFoundFuture = future;
        discoveryAgent.searchServices(null,new UUID[]{serviceUUID},device,listener);
        var timeoutTask = scheduler.schedule(()->{
            serviceFoundFuture.completeExceptionally(new TimeoutException());
        }, 30, TimeUnit.SECONDS);
        serviceFoundFuture.exceptionally(e->{
            synchronized (this){
                isPairing = false;
            }
            timeoutTask.cancel(true);
            log.error("[Bt Pairing Service] throw",e);
            return null;
        });
        return serviceFoundFuture.thenApply(btServices -> {
            synchronized (this){
                isPairing = false;
            }
//            timeoutTask.cancel(true);
            return btServices;
        });
    }

    class StandardDiscoveryListener implements DiscoveryListener {
        @Override
        public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
            try {
                BtInfo btInfo = new BtInfo(btDevice);
                System.out.println("Discovered device: "+btInfo);
                allBtInfos.put(btInfo,btDevice);
            } catch (IOException e) {
                log.error("[Bt Discovering] throw",e);
            }
        }

        @Override
        public void inquiryCompleted(int discType) {
            log.info("Inquiry completed. discType: {}",discType);
            scanning.set(false);
        }

        @Override
        public void servicesDiscovered(int i, ServiceRecord[] serviceRecords) {
            if(serviceFoundFuture == null) {
                return;
            }
            var discoveredServices = new HashSet<BtService>();
            for (ServiceRecord serviceRecord : serviceRecords) {
                try {
                    var device = deviceProvider.getObject(serviceRecord,serviceUUID);
                    discoveredServices.add(device);
                } catch (BeanCreationException e) {
                    log.warn("can not pair device service. address: {}",serviceRecord.getHostDevice().getBluetoothAddress(),e);
                    return;
                }
                log.info("found service {}",serviceRecord);
            }
            serviceFoundFuture.complete(discoveredServices);
        }

        @Override
        public void serviceSearchCompleted(int transID, int respCode) {
            log.info("Service search completed. transID: {} respCode: {}",transID,respCode);
        }
    }
}
